// 定义全局变量
let _Vue = null
export default class VueRouter {
  /* 实现 Vue 插件机制 */
  static install (Vue) {
    /* 1. 判断插件是否已安装 */
    if (VueRouter.install.installed) {
      return
    }
    VueRouter.install.installed = true // 设置值为 true 表示已安装
    /* 2. 将 Vue 构造函数记录到全局变量 */
    _Vue = Vue
    /* 3. 把创建的 Vue 实例时传递的 router 对象，注入到所有 Vue 实例上(想要所有实例共享一个成员，想到了原型) */
    // _Vue.prototype.$router = this.$options.router // 谁调用则this 指向谁，目前指向 VueRouter, 非 Vue 实例

    /**
     * 3.1 混入
     * 在插件内可给所有 Vue 实例混入一个选项，选项中设置一个 beforeCreate 钩子函数，在这里可获取到 Vue 实例，并在原型上注入 $router
     * 这样写会给 Vue 所有实例（组件）加入 beforeCreate，导致 beforeCreate 执行很多次，还需判断
     */
    _Vue.mixin({
      beforeCreate () {
        // 这里的 this 就是 Vue 实例，判断若选项中有 router 则执行，否则认为调用的是组件，不执行
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router

          /** 初始化 */
          this.$options.router.init()
        }
      }
    })
  }

  /**
   * 构造函数
   * @param {*} options
   * options: 记录构造函数中传入的属性
   * routerMap: 把 options 传递的 routes（路由规则）解析并存储到routerMap，key 路由地址，value路由组件
   * data:  响应式对象，可使用 Vue 中的 observable 创建
   */
  constructor (options) {
    this.options = options
    this.routerMap = {}
    this.data = _Vue.observable({
      current: '/' // 存储当前路由地址，默认 /
    })
  }

  /* init 包装 createRouterMap & initComponents 方法 */
  init () {
    this.createRouterMap()
    this.initComponents(_Vue)
    this.initEvent()
  }

  /**
   * createRouterMap
   * 作用：遍历所有路由规则，并解析为键值对的形式存储到 routerMap 对象中
   * 路由规则都在 options 中的属性 routes 中
   */
  createRouterMap () {
    this.options.routes.forEach(route => {
      // 取出路径 & 组件作为 routerMap 的属性&值
      this.routerMap[route.path] = route.component
    })
  }

  /**
   * initComponents
   * @param {*} Vue Vue 的构造函数，可不传，但传了可以减少这个方法与外部的依赖
   */
  initComponents (Vue) {
    // props 接受外部传入的参数
    Vue.component('router-link', {
      props: {
        to: String // 跳转的链接
      },
      // template: '<a :href="to"><slot></slot></a>' // 展示模板, slot 是插槽，使用完整版需要在 vue.config.js 开启 runtimeCompiler
      // render 函数: h 用来创建虚拟 DOM
      render (h) {
        /**
         * 调用 h 函数, 返回虚拟 DOM
         * @param {string} param1 创建元素对应的选择器（可使用标签选择器）
         * @param {object} param2 给标签设置属性（dom对象的属性）
         * @param {array} param3 生成元素中的子元素
         */
        return h(
          'a', {
            attrs: {
              href: this.to
            },
            on: {
              click: this.clickHandler // 不要加括号，否则就为调用，这里只是注册
            }
          }, [this.$slots.default] // 获取默认插槽
        )
      },
      methods: {
        clickHandler (e) {
          /**
           * 改变浏览器地址栏，且不向服务器发请求
           * @param {any} param1-data 触发 popState 时传递的事件对象
           * @param {string} param2-title 网页标题
           * @param {string} param3-url 超链接要跳转的地址
           */
          history.pushState({}, '', this.to)
          this.$router.data.current = this.to // 响应式对象，因此重新赋值会重新渲染
          e.preventDefault() // 阻止事件默认行为
        }
      }
    })

    // router-view
    const self = this
    Vue.component('router-view', {
      render (h) {
        /**
         * 找到当前路由地址，去 routerMap 对象中找到对应组件，调用 h 函数，返回虚拟 DOM
         * 构造函数中 data 对象下的 current 属性为当前路由地址
         * 期望 this 是路由对象，在render 方法中，this 不再是 VueRouter 实例，需在外层将 this 存起来
         */
        const component = self.routerMap[self.data.current] // 当前路由组件
        return h(component)
      }
    })
  }

  /* initEvent 处理操作浏览器前进后退不渲染页面的问题 */
  initEvent () {
    window.addEventListener('popstate', () => {
      // 把当前地址栏的地址路径部分取出，并存储到 this.data.current 中
      this.data.current = window.location.pathname // 获取到斜杠/后的内容
    })
  }
}
